package com.billdimmick.merkabah;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.SAXException;

public class MockSQSManager extends SQSManager {
	private final static String template = "<ErrorResponse><Error><Type>%s</Type><Code>%s</Code>"
			+ "<Message>%s</Message></Error><RequestId>%s</RequestId></ErrorResponse>";

	private HttpMethod presetResponse;
	private int code = 200;
	private String body;
	private HttpMethod response;
	private final Set<String> queues = new HashSet<String>();
	private final Map<String, Integer> timeouts = new HashMap<String, Integer>();
	private final Map<String, Map<String, Message>> messages = new HashMap<String, Map<String, Message>>();
	private final Map<String, String> reservations = new HashMap<String, String>();

	public MockSQSManager() {
		super(new BasicSQSSecurityAdapter("mock", "mock"));
		final BasicSQSSecurityAdapter security=(BasicSQSSecurityAdapter)this.getSecurity();
		security.setEncryptionKeyName("default-key");
		security.setSigningKeyName("default-sign");		
		security.generateKeypair(security.getEncryptionKeyName());
		security.generateKeypair(security.getSigningKeyName());
	}
	
	public MockSQSManager(final SQSSecurityAdapter credentials) {
		super(credentials);
	}

	public void returnError(final String type, final String errorCode,
			final String errorMessage, final String requestId) {
		this.body = String.format(template, type, errorCode, errorMessage,
				requestId);
		this.code = 400;
	}

	public void createCommunicationException() {
		assignPresetResponse(new IOExceptionHttpMethod());
	}
	
	@Override
	public void createQueue(String name, int timeout) throws SQSException {
		if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<CreateQueueResponse\n\t"
							+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"CreateQueueResponse\">\n\t"
							+ "<CreateQueueResult>");
			body.append("<QueueUrl>");
			body.append("http://queue.amazonaws.com/");
			body.append(name);
			body.append("</QueueUrl>");
			body.append("</CreateQueueResult>");
			body.append("<ResponseMetadata><RequestId>");
			body.append(UUID.randomUUID().toString());
			body.append("</RequestId></ResponseMetadata>");
			body.append("</CreateQueueResponse>");
			this.body = body.toString();
		}
		super.createQueue(name, timeout);
		final String realname=getRequestQueryParameters().get("QueueName");
		queues.add(realname);
		messages.put(realname, new LinkedHashMap<String, Message>());
		timeouts.put(realname, timeout);
	}

	@Override
	public void deleteQueue(String name) throws SQSException {
		if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<DeleteQueueResponse\n\t"
							+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"DeleteQueueResponse\">\n\t" + "");
			body.append("<ResponseMetadata><RequestId>");
			body.append(UUID.randomUUID().toString());
			body.append("</RequestId></ResponseMetadata>");
			body.append("</DeleteQueueResponse>");
			this.body = body.toString();
		}
		super.deleteQueue(name);
		final String realname=getQueueName();
		queues.remove(realname);
		timeouts.remove(realname);
	}

	@Override
	public Collection<String> listQueues(String prefix) throws SQSException {
		if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<ListQueuesResponse\n\t"
					+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"ListQueuesResponse\">\n\t"
							+ "<ListQueuesResult>");
			for (String queue : queues) {
				if (prefix == null || queue.startsWith(prefix)) {
					body.append("<QueueUrl>");
					body.append("http://queue.amazonaws.com/");
					body.append(queue);
					body.append("</QueueUrl>");
				}
			}
			body.append("</ListQueuesResult>");
			body.append("<ResponseMetadata><RequestId>");
			body.append(UUID.randomUUID().toString());
			body.append("</RequestId></ResponseMetadata>");
			body.append("</ListQueuesResponse>");
			this.body = body.toString();
		}
		return super.listQueues(prefix);
	}

	@Override
	public int getApproximateNumberOfMessages(String name) throws SQSException {
		final String queueName=super.createQueueName(name);
		final Map<String, Message> queue = messages.get(queueName);
		final String requestId=UUID.randomUUID().toString();
		if (queue==null) {
			this.returnError("Sender", "NoSuchQueue", "No such queue.", requestId);
		} else if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<GetQueueAttributesResponse\n\t"
						+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
						+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
						+ "xsi:type=\"ListQueuesResponse\">\n\t"
						+ "<GetQueueAttributesResult>");
			body.append("<Attribute>");
			body.append("<Name>ApproximateNumberOfMessages</Name>");
			body.append("<Value>");
			body.append(queue.size());
			body.append("</Value>");
			body.append("</Attribute>");
			body.append("</GetQueueAttributesResult>");
			body.append("</GetQueueAttributesResponse>");
			this.body = body.toString();
		}
		return super.getApproximateNumberOfMessages(name);
	}
	
	@Override
	public void send(Message message) throws SQSException {
		Message stored = null;
		final String queueName=super.createQueueName(message.getQueue());
		final Map<String, Message> queue = messages.get(queueName);
		final String requestId=UUID.randomUUID().toString();
		if (queue==null) {
			this.returnError("Sender", "NoSuchQueue", "No such queue.", requestId);
		} else if (code == 200) {
			final String md5 = new String(DigestUtils.md5Hex(message
					.getMessage()));
			final String id = UUID.randomUUID().toString();
			stored = new Message(message.getQueue(), super.formatMessage(message.getMessage()), id, null);
			final StringBuilder body = new StringBuilder(
					"<SendMessageResponse\n\t"
							+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"SendMessageResponse\">\n\t" + "");
			body.append("<SendMessageResult>");
			body.append("<MD5OfMessageBody>");
			body.append(md5);
			body.append("</MD5OfMessageBody>");
			body.append("<MessageId>");
			body.append(id);
			body.append("</MessageId>");
			body.append("</SendMessageResult>");
			body.append("<ResponseMetadata><RequestId>");
			body.append(requestId);
			body.append("</RequestId></ResponseMetadata>");
			body.append("</SendMessageResponse>");
			this.body = body.toString();
		}
		super.send(message);
		if (stored != null) {
			queue.put(stored.getMessageId(), stored);
		}
	}

	@Override
	public Collection<Message> receive(final String queue, final int max)
			throws SQSException {
		Collection<Message> result;
		final String queueName = super.createQueueName(queue);
		Map<String, Message> q = messages.get(queueName);
		if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<ReceiveMessageResponse\n\t"
							+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"ReceiveMessageResponse\">\n\t" + "");
			body.append("<ReceiveMessageResult>");
			Iterator<Map.Entry<String, Message>> iter = q.entrySet().iterator();
			for (int i = 0; i < max && iter.hasNext(); i++) {
				Map.Entry<String, Message> entry = iter.next();
				String reciept = UUID.randomUUID().toString();
				final String md5 = new String(DigestUtils.md5Hex(entry
						.getValue().getMessage()));
				this.reservations.put(reciept, entry.getValue().getMessageId());
				body.append("<Message>");
				body.append("<MessageId>");
				body.append(entry.getValue().getMessageId());
				body.append("</MessageId>");
				body.append("<ReceiptHandle>");
				body.append(reciept);
				body.append("</ReceiptHandle>");
				body.append("<MD5OfBody>");
				body.append(md5);
				body.append("</MD5OfBody>");
				body.append("<Body>");
				//TODO: What happens if the private key disappears before this call?
				body.append(entry.getValue().getMessage());
				body.append("</Body>");
				body.append("</Message>");
			}
			body.append("</ReceiveMessageResult>");
			body.append("<ResponseMetadata><RequestId>");
			body.append(UUID.randomUUID().toString());
			body.append("</RequestId></ResponseMetadata>");
			body.append("</ReceiveMessageResponse>");
			this.body = body.toString();
		}
		result = super.receive(queue, max);
		return result;
	}

	@Override
	public void delete(Message message) throws SQSException {
		if (code == 200) {
			final StringBuilder body = new StringBuilder(
					"<DeleteMessageResponse\n\t"
							+ "xmlns=\"http://queue.amazonaws.com/doc/2008-01-01/\"\n\t"
							+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t"
							+ "xsi:type=\"DeleteMessageResponse\">\n\t" + "");
			body.append("<ResponseMetadata><RequestId>");
			body.append(UUID.randomUUID().toString());
			body.append("</RequestId></ResponseMetadata>");
			body.append("</DeleteMessageResponse>");
			this.body = body.toString();
		}
		super.delete(message);
		final String rid = message.getReceiptHandle();
		final String mid = this.reservations.get(rid);
		final Map<String, Message> q = this.messages.get(getQueueName());
		q.remove(mid);
	}

	@Override
	protected HttpMethod submit(String queue, Map<String, String> parameters)
			throws IOException, SQSException, ParserConfigurationException,
			SAXException {
		if (this.presetResponse != null) {
			this.response=this.presetResponse;
		} else {
			final HttpMethod method = super.makeMethod(queue, parameters);
			this.response = new ControlledHttpMethod(code, body);
			this.response.setURI(method.getURI());
			this.response.setQueryString(method.getQueryString());		
		}
		this.presetResponse=null;
		return this.response;
	}
	
	public void assignPresetResponse(final HttpMethod response) {
		this.presetResponse = response;
	}
	
	
	
	public Map<String, String> getRequestQueryParameters() {
		Map<String, String> result=new HashMap<String, String>();
		String [] pairs=this.response.getQueryString().split("&");
		for (String pair: pairs) {
			String [] split=pair.split("=");
			result.put(split[0], split[1]);
		}
		return result;
	}	
	
	public String getQueueName() {
		return StringUtils.defaultIfEmpty(this.response.getPath().replaceFirst("/", ""), null);
	}

	
	
	public static class ControlledHttpMethod extends HttpMethodBase {
		private static final Log log=LogFactory.getLog(ControlledHttpMethod.class);
		private int statusCode;
		private InputStream body;
		
		public ControlledHttpMethod(final int statusCode, final String body) {
			this(statusCode, new ByteArrayInputStream(body.getBytes()));
		}
		
		public ControlledHttpMethod(final int statusCode, final InputStream body) {
			Validate.isTrue(statusCode>0);
			Validate.notNull(body);
			this.statusCode=statusCode;
			this.body=body;
		}
		
		@Override
		public String getName() {
			return "GET";
		}
		
		@Override
		public int getStatusCode() {
			return this.statusCode;
		}
		
		@Override
		public InputStream getResponseBodyAsStream() throws IOException {
			return body;
		}
	}

	public static class IOExceptionHttpMethod extends HttpMethodBase {
		@Override
		public String getName() {
			return "GET";
		}
		
		@Override
		public InputStream getResponseBodyAsStream() throws IOException {
			throw new IOException();
		}
	}	

}
